#include "edgyconfig.h"

#include <QHeaderView>
#include <QTimer>

// define the globals
const QString GUIConfigGlobals::CAPTURE_KEY_LABEL = "Capture keystroke";
const QString GUIConfigGlobals::WINDOW_CLASS_KEY_LABEL = "Select a window";
const QString GUIConfigGlobals::MENU_ITEM_KEY_ACTION_LABEL = "press a key";
const QString GUIConfigGlobals::MENU_ITEM_CMD_ACTION_LABEL = "run a command";
const QString GUIConfigGlobals::MENU_ITEM_NO_ACTION_LABEL = "no action (spacer)";
const QString GUIConfigGlobals::EFFECT_KEY_ACTION_LABEL = GUIConfigGlobals::MENU_ITEM_KEY_ACTION_LABEL;
const QString GUIConfigGlobals::EFFECT_SKEY_ACTION_LABEL = "press a stateful key";
const QString GUIConfigGlobals::EFFECT_CMD_ACTION_LABEL = GUIConfigGlobals::MENU_ITEM_CMD_ACTION_LABEL;
const QString GUIConfigGlobals::EFFECT_MENU_ACTION_LABEL = "show a menu";
const QString GUIConfigGlobals::EFFECT_GESTURES_ACTION_LABEL = "show a gestural pie chart";

EdgyConfig::EdgyConfig(QMainWindow *parent=0) : QMainWindow(parent), parent(parent), configDirty(false) {
  setupUi(parent);
  
  // init X
  xdpy = XOpenDisplay(NULL);
  xscreen = DefaultScreen(xdpy);
  xroot = DefaultRootWindow(xdpy);
  
  // init edgy
  configEdgy();
  
  // allow dragging inside the cursors list
  cCursorsList->setDragEnabled(true);
  cCursorsList->viewport()->setAcceptDrops(true);
  cCursorsList->setDropIndicatorShown(true);
  cCursorsList->setDragDropMode(QAbstractItemView::InternalMove);
  
  // stretch table headers
  mTable->setColumnWidth(0, 150);
  mTable->horizontalHeader()->setResizeMode(0, QHeaderView::Interactive);
  mTable->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
  eTable->setColumnWidth(0, 160);
  eTable->setColumnWidth(1, 100);
  eTable->horizontalHeader()->setResizeMode(0, QHeaderView::Interactive);
  eTable->horizontalHeader()->setResizeMode(1, QHeaderView::Interactive);
  eTable->horizontalHeader()->setResizeMode(2, QHeaderView::Stretch);
  
  // initialize the dialog. we need to pull data from it, so don't destroy it.
  cursorSelectDlg = new CursorSelectDlg(this);
  
  // set the effects key capturing fields properly. the modifier only accepts the modifiers.
  // the keys only accept regular keys, since it's highly unlikely that pressing two modifier
  // keys will cause any sort of change in any application.
  eskModLineEdit->ignoreRegulars(true);
  eskK1LineEdit->ignoreModifiers(true);
  eskK2LineEdit->ignoreModifiers(true);
  
  creatingNewEffectItem = false;
  creatingNewMenuItem = false;
  
  parser = NULL;
  
  // connect slots to signals
  // cursors tab
  QObject::connect(cNewCursorBtn, SIGNAL(clicked()), this, SLOT(createCursor()));
  QObject::connect(cEditCursorBtn, SIGNAL(clicked()), this, SLOT(editCursor()));
  QObject::connect(cDeleteCursorBtn, SIGNAL(clicked()), this, SLOT(deleteCursor()));
  QObject::connect(cCursorsList, SIGNAL(itemSelectionChanged()), this,
          SLOT(toggleCursorListButtons()));
  QObject::connect(cCursorSetCombo, SIGNAL(currentIndexChanged(int)), this,
          SLOT(toggleCursorSetButtons()));
  QObject::connect(cNewCursorSetBtn, SIGNAL(clicked()), this, SLOT(createCursorSet()));
  QObject::connect(cRenameCursorSetBtn, SIGNAL(clicked()), this, SLOT(renameCursorSet()));
  QObject::connect(cDeleteCursorSetBtn, SIGNAL(clicked()), this, SLOT(deleteCursorSet()));
  QObject::connect(cCursorSetCombo, SIGNAL(currentIndexChanged(const QString&)), this,
          SLOT(loadCursors(const QString&)));
  
  // menus tab
  QObject::connect(mNewMenuBtn, SIGNAL(clicked()), this, SLOT(createMenu()));
  QObject::connect(mRenameMenuBtn, SIGNAL(clicked()), this, SLOT(renameMenu()));
  QObject::connect(mDeleteMenuBtn, SIGNAL(clicked()), this, SLOT(deleteMenu()));
  QObject::connect(mNewBtn, SIGNAL(clicked()), this, SLOT(createMenuItem()));
  QObject::connect(mEditBtn, SIGNAL(clicked()), this, SLOT(editMenuItem()));
  QObject::connect(mDeleteBtn, SIGNAL(clicked()), this, SLOT(deleteMenuItem()));
  QObject::connect(mTable, SIGNAL(itemSelectionChanged()), this, SLOT(toggleMenuItemsButtons()));
  QObject::connect(mMenuCombo, SIGNAL(currentIndexChanged(int)), this,
          SLOT(toggleMenuListButtons()));
  QObject::connect(mSaveItemBtn, SIGNAL(clicked()), this, SLOT(saveMenuItem()));
  QObject::connect(mkKeyCaptureBtn, SIGNAL(clicked()), this, SLOT(openMenuKeyCaptureDlg()));
  QObject::connect(mActionCombo, SIGNAL(currentIndexChanged(int)), this,
          SLOT(toggleMenuActionStack()));
  
  // effects tab
  QObject::connect(eNewBtn, SIGNAL(clicked()), this, SLOT(createEffect()));
  QObject::connect(eEditBtn, SIGNAL(clicked()), this, SLOT(editEffect()));
  QObject::connect(eDeleteBtn, SIGNAL(clicked()), this, SLOT(deleteEffect()));
  QObject::connect(eWinClassBtn, SIGNAL(clicked()), this, SLOT(selectWindow()));
  QObject::connect(eTable, SIGNAL(itemSelectionChanged()), this, SLOT(toggleEffectListButtons()));
  QObject::connect(eSaveBtn, SIGNAL(clicked()), this, SLOT(saveEffect()));
  QObject::connect(ekKeyCaptureBtn, SIGNAL(clicked()), this, SLOT(openEffectKeyCaptureDlg()));
  
  
  // actions
  QObject::connect(action_Save_Config, SIGNAL(triggered()), this, SLOT(saveConfig()));
  QObject::connect(action_Save_Config_As, SIGNAL(triggered()), this, SLOT(saveConfigAs()));
  QObject::connect(action_Open_Config, SIGNAL(triggered()), this, SLOT(loadConfig()));
}

EdgyConfig::~EdgyConfig() {
  delete cursorSelectDlg;
}


/********* CURSORS TAB *********/

void EdgyConfig::createCursorSet() {
  bool ok;
  QString text = QInputDialog::getText(this, "New Cursor Set",
          "Name of the new cursor set:", QLineEdit::Normal,
          NULL, &ok);
  if (ok && !(text.trimmed().isEmpty())) {
    int existingItem;
    if ((existingItem = cCursorSetCombo->findText(text)) != -1) {
      // trying to overwrite existing set. complain.
      warning("This cursor set already exists.");
      cCursorSetCombo->setCurrentIndex(existingItem);
      return;
    }
    cCursorSetCombo->addItem(text);
    cCursorSetCombo->setCurrentIndex(cCursorSetCombo->count() - 1);
  }
}

void EdgyConfig::renameCursorSet() {
  int cur = cCursorSetCombo->currentIndex();
  QString curText = cCursorSetCombo->currentText();
  bool ok;
  QString text = QInputDialog::getText(this, "Rename Cursor Set",
          "New name of the cursor set:", QLineEdit::Normal,
          cCursorSetCombo->currentText(), &ok);
  if (ok && !(text.trimmed().isEmpty()) && text != curText) {
    int existingItem;
    if ((existingItem = cCursorSetCombo->findText(text)) != -1) {
      // renaming to an existing item. confirm.
      int ret = QMessageBox::warning(this, "EdgyConfigGUI",
              "You're about to overwrite an existing cursor set. Proceed?",
              QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
      if (ret == QMessageBox::Yes) {
        // remove existing choice. the conf will be overwritten anyway
        cCursorSetCombo->removeItem(existingItem);
        egCursorCombo->removeItem(existingItem);
        cur = cCursorSetCombo->currentIndex();
      } else return;
    }
    
    // update the existing effect cursorsets
    int cPos;
    bool reloadEffects = false;
    while ((cPos = conf.findEffectWithCursorSet(curText)) >= 0) {
      conf.renameCursorSetInEffect(cPos, text);
      reloadEffects = true;
    }
    if (reloadEffects)
      loadEffects(); // reload the effects table
    
    cCursorSetCombo->setItemText(cur, text);
    conf.renameCursorSet(curText, text);
    
    // update in effects as well
    egCursorCombo->setItemText(cur, text);
    
    configDirty = true;
  }
}

void EdgyConfig::deleteCursorSet() {
  int cur = cCursorSetCombo->currentIndex();
  QString set = cCursorSetCombo->currentText();
  
  int efPos;
  if ((efPos = conf.findEffectWithCursorSet(set)) >= 0) {
    int answer = QMessageBox::warning(this, "EdgyConfigGui",
            QString("This cursor set is used in effect #%1.\nDeleting it will also "
            "delete the effect.\nProceed?").arg(efPos+1),
            QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
    if (answer == QMessageBox::Yes) {
      conf.deleteEffectItem(efPos);
      eTable->removeRow(efPos);
    } else return;
  }
  
  cCursorsList->clear();
  cCursorSetCombo->removeItem(cur);
  egCursorCombo->removeItem(cur); // update in effects too
  if (cCursorSetCombo->count() > 0)
    cCursorSetCombo->setCurrentIndex(0);
  else
    cCursorSetCombo->setCurrentIndex(-1);
  
  conf.deleteCursorSet(set);
  
  configDirty = true;
}

void EdgyConfig::createCursor() {
  int ret = cursorSelectDlg->exec();
  if (ret == QDialog::Accepted) {
    QListWidgetItem* newitem = new QListWidgetItem(*cursorSelectDlg->cursorList->currentItem());
    cCursorsList->addItem(newitem);
    cCursorsList->setCurrentItem(newitem);
    saveCursors();
  }
}

void EdgyConfig::editCursor() {
  // try to focus on the selected item in the cursors dialog
  QListWidgetItem* selectedItem = cCursorsList->currentItem();
  if (selectedItem != NULL) {
    QList<QListWidgetItem*> matchingItems = cursorSelectDlg->cursorList->findItems(
            selectedItem->text(), Qt::MatchExactly);
    if (matchingItems.size() > 0) {
      cursorSelectDlg->cursorList->setCurrentItem(matchingItems[0]);
    }
  }
  
  int ret = cursorSelectDlg->exec();
  if (ret == QDialog::Accepted) {
    QListWidgetItem* newitem = new QListWidgetItem(*cursorSelectDlg->cursorList->currentItem());
    int row = cCursorsList->currentRow();
    cCursorsList->takeItem(row);
    cCursorsList->insertItem(row, newitem);
    cCursorsList->setCurrentRow(row);
    saveCursors();
  }
}

void EdgyConfig::deleteCursor() {
  int selectedItem = cCursorsList->currentRow();
  if (selectedItem != -1) {
    cCursorsList->takeItem(selectedItem);
    if (selectedItem != 0)
      cCursorsList->setCurrentRow(selectedItem-1);
    else
      cCursorsList->setCurrentRow(-1);
    saveCursors();
  }
}

void EdgyConfig::toggleCursorListButtons() {
  if (cCursorsList->selectedItems().size() == 0) {
    cEditCursorBtn->setEnabled(false);
    cDeleteCursorBtn->setEnabled(false);
  } else {
    cEditCursorBtn->setEnabled(true);
    cDeleteCursorBtn->setEnabled(true);
  }
}

void EdgyConfig::toggleCursorSetButtons() {
  if (cCursorSetCombo->currentIndex() == -1) {
    cRenameCursorSetBtn->setEnabled(false);
    cDeleteCursorSetBtn->setEnabled(false);
    cNewCursorBtn->setEnabled(false);
  } else {
    cRenameCursorSetBtn->setEnabled(true);
    cDeleteCursorSetBtn->setEnabled(true);
    cNewCursorBtn->setEnabled(true);
  }
}

void EdgyConfig::saveCursors() {
  QString cursorSetName = cCursorSetCombo->currentText();
  QStringList list;
  for (int i = 0; i < cCursorsList->count(); i++)
    list.append(cCursorsList->item(i)->text());
  
  conf.addCursorSet(cursorSetName, list);
  
  // update in effects as well
  if (egCursorCombo->findText(cursorSetName) == -1)
    egCursorCombo->addItem(cursorSetName);
  
  configDirty = true;
}

void EdgyConfig::loadCursors(const QString& name) {
  cCursorsList->clear();
  
  QStringList cursorList = conf.cursors(name);
  QList<QListWidgetItem*> widgets = createListItemsFromCursors(*this, cursorList);
  
  QList<QListWidgetItem*>::const_iterator i;
  for (i = widgets.begin(); i != widgets.end(); i++)
    cCursorsList->addItem(*i);
}



/********* MENUS TAB *********/

void EdgyConfig::createMenu() {
  bool ok;
  QString text = QInputDialog::getText(this, "New Menu",
          "Name of the new menu:", QLineEdit::Normal,
          NULL, &ok);
  if (ok && !(text.trimmed().isEmpty())) {
    int existingItem;
    if ((existingItem = mMenuCombo->findText(text)) != -1) {
      // creating a duplicate. complain
      warning("This menu already exists.");
      mMenuCombo->setCurrentIndex(existingItem);
      return;
    }
    mMenuCombo->addItem(text);
    mMenuCombo->setCurrentIndex(mMenuCombo->count() - 1);
  }
}

void EdgyConfig::renameMenu() {
  int cur = mMenuCombo->currentIndex();
  QString curText = mMenuCombo->currentText();
  bool ok;
  QString text = QInputDialog::getText(this, "Rename Menu",
          "New name of the menu:", QLineEdit::Normal,
          mMenuCombo->currentText(), &ok);
  if (ok && !(text.trimmed().isEmpty()) && text != curText) {
    int existingItem;
    if ((existingItem = mMenuCombo->findText(text)) != -1) {
      // renaming to an existing item. confirm
      int ret = QMessageBox::warning(this, "EdgyConfigGUI",
              "You're about to overwrite an existing menu. Proceed?",
              QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
      if (ret == QMessageBox::Yes) {
        // remove existing choice. the conf will be overwritten anyway
        mMenuCombo->removeItem(existingItem);
        emMenuCombo->removeItem(existingItem);
        egMenuCombo->removeItem(existingItem);
        cur = mMenuCombo->currentIndex();
      } else return;
    }
    clearEditMenuItemFrame();
    mMenuCombo->setItemText(cur, text);
    
    
    // update the existing effect menus
    int mPos;
    bool reloadEffects = false;
    while ((mPos = conf.findEffectWithMenu(curText)) >= 0) {
      conf.renameMenuInEffect(mPos, text);
      reloadEffects = true;
    }
    if (reloadEffects)
      loadEffects(); // reload the effects table

    // update in effects as well
    emMenuCombo->setItemText(cur, text);
    egMenuCombo->setItemText(cur, text);
    conf.renameMenu(curText, text);
    
    configDirty = true;
  }
}

void EdgyConfig::deleteMenu() {
  int cur = mMenuCombo->currentIndex();
  QString menu = mMenuCombo->currentText();
  
  int efPos;
  if ((efPos = conf.findEffectWithMenu(menu)) >= 0) {
    int answer = QMessageBox::warning(this, "EdgyConfigGui",
            QString("This menu is used in effect #%1.\nDeleting it will also delete the effect.\n"
            "Proceed?").arg(efPos+1), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
    if (answer == QMessageBox::Yes) {
      conf.deleteEffectItem(efPos);
      eTable->removeRow(efPos);
    } else return;
  }
  
  mTable->clearContents();
  mTable->setRowCount(0);
  mMenuCombo->removeItem(cur);
  
  conf.deleteMenu(menu);
  
  clearEditMenuItemFrame();
  mEditItemGroup->setEnabled(false);
  
  configDirty = true;
}

void EdgyConfig::createMenuItem() {
  mEditItemGroup->setEnabled(true);
  mTable->clearSelection();
  mTable->reset();
  
  creatingNewMenuItem = true;
  
  mLabelLine->setFocus();
}

void EdgyConfig::editMenuItem() {
  clearEditMenuItemFrame();
  
  if (mTable->currentRow() != -1) {
    ConfigMenuItem* cmi = conf.menuItem(mMenuCombo->currentText(), mTable->currentRow());
    if (cmi == NULL) {
      warning("Internal error: item doesn't exist.");
      return;
    }
    
    if (ConfigMenuItem_Key* key = dynamic_cast<ConfigMenuItem_Key*>(cmi)) {
      if (!setActionComboToText(mActionCombo, GUIConfigGlobals::MENU_ITEM_KEY_ACTION_LABEL))
        return;
      
      mLabelLine->setText(key->label);
      QStringList keys;
      keys.append(key->k1);
      if (key->k2 != "")
        keys.append(key->k2);
      if (key->k3 != "")
        keys.append(key->k3);
      mkKeyCaptureBtn->setKeys(keys);
      if (key->p) {
        int ppos = mkMoveCursorCombo->findText(key->p->name().c_str(), Qt::MatchExactly);
        if (ppos == -1) {
          warning("Internal error: can't set location");
          return;
        }
        mkMoveCursorCombo->setCurrentIndex(ppos);
      }
      
      mActionStack->setCurrentIndex(0);
      
    } else if (ConfigMenuItem_Cmd* cmd = dynamic_cast<ConfigMenuItem_Cmd*>(cmi)) {
      if (!setActionComboToText(mActionCombo, GUIConfigGlobals::MENU_ITEM_CMD_ACTION_LABEL))
        return;
      
      mLabelLine->setText(cmd->label);
      mcCmdLine->setText(cmd->cmd);
      mActionStack->setCurrentIndex(1);
    } else if (ConfigMenuItem_NoAction* noact = dynamic_cast<ConfigMenuItem_NoAction*>(cmi)) {
      if (!setActionComboToText(mActionCombo, GUIConfigGlobals::MENU_ITEM_NO_ACTION_LABEL))
        return;
      
      mLabelLine->setText(noact->label);
      mActionStack->setCurrentIndex(2);
    } else {
      warning("Can't have this type of item in a menu.");
      return;
    }
  }
  
  mEditItemGroup->setEnabled(true);
  
  mLabelLine->setFocus();
}

void EdgyConfig::deleteMenuItem() {
  mEditItemGroup->setEnabled(false);
  clearEditMenuItemFrame();
  
  int curRow = mTable->currentRow();
  if (curRow != -1) {
    conf.deleteMenuItem(mMenuCombo->currentText(), curRow);
    mTable->removeRow(curRow);
    if (mTable->rowCount() > curRow)
      mTable->selectRow(curRow);
    else if (mTable->rowCount() == curRow)
      mTable->selectRow(curRow-1);
    
    configDirty = true;
  }
}

void EdgyConfig::openMenuKeyCaptureDlg() {
  KeyCaptureDlg dlg(this);
  dlg.keyLineEdit->ignoreModifiers(true);
  dlg.m1LineEdit->ignoreRegulars(true);
  dlg.m2LineEdit->ignoreRegulars(true);
  if (dlg.exec() == QDialog::Accepted) {
    QStringList keys = dlg.keys();
    mkKeyCaptureBtn->setKeys(keys);
    dlg.resetKeys();
  }
}

void EdgyConfig::toggleMenuListButtons() {
  if (mMenuCombo->currentIndex() == -1) {
    mRenameMenuBtn->setEnabled(false);
    mDeleteMenuBtn->setEnabled(false);
    mNewBtn->setEnabled(false);
  } else {
    mRenameMenuBtn->setEnabled(true);
    mDeleteMenuBtn->setEnabled(true);
    mNewBtn->setEnabled(true);
    
    loadMenu(mMenuCombo->currentText());
  }
}

void EdgyConfig::toggleMenuItemsButtons() {
  if (mTable->selectedItems().size() == 0) {
    mEditBtn->setEnabled(false);
    mDeleteBtn->setEnabled(false);
  } else {
    mEditBtn->setEnabled(true);
    mDeleteBtn->setEnabled(true);
  }
}

void EdgyConfig::toggleMenuActionStack() {
  if (mActionCombo->currentText() == GUIConfigGlobals::MENU_ITEM_NO_ACTION_LABEL)
    mActionStack->setEnabled(false);
  else
    mActionStack->setEnabled(true);
}

void EdgyConfig::saveMenuItem() {
  QString menuName = mMenuCombo->currentText();
  
  if (menuName.isNull()) {
    warning("Menu item label was not specified.");
    return;
  }
  
  QString label, wClass, moveTo, cmd;
  QStringList keys;
  
  int selectedRow = creatingNewMenuItem ? -1 : mTable->currentRow();
  creatingNewMenuItem = false;
  
  switch (mActionCombo->currentIndex()) {
    case 0: // press a key
      if (mkKeyCaptureBtn->text() == GUIConfigGlobals::CAPTURE_KEY_LABEL) {
        warning("Key was not specified.");
        return;
      } else if ((label = mLabelLine->text()).trimmed() == "") {
        warning("Label was not specified.");
        return;
      }
      moveTo = mkMoveCursorCombo->currentText();
      
      keys = mkKeyCaptureBtn->getKeys();
      if (keys.size() == 0) {
        warning("No keystrokes specified for the key.");
        return;
      }
      
      conf.setMenuItem_Key(menuName, label, keys,
              (mkMoveCursorCombo->currentIndex() == 0 ? NULL : moveTo), selectedRow);
      break;
    
    case 1: // run a command
      if ((label = mLabelLine->text()).trimmed() == "") {
        warning("Label was not specified.");
        return;
      }
      
      if ((cmd = mcCmdLine->text()).trimmed() == "") {
        warning("Command is empty");
        return;
      }
      
      conf.setMenuItem_Cmd(menuName, label, cmd, selectedRow);
      break;
    
    case 2: // no action
      if ((label = mLabelLine->text()).trimmed() == "") {
        warning("Label was not specified.");
        return;
      }
      
      conf.setMenuItem_NoAction(menuName, label, selectedRow);
      break;
    
    default:
      // shouldn't happen. TODO: log this somewhere
      warning("Unknown action selected.");
      return;
  }
  
  // update in effects as well
  if (emMenuCombo->findText(menuName) == -1)
    emMenuCombo->addItem(menuName);
  if (egMenuCombo->findText(menuName) == -1)
    egMenuCombo->addItem(menuName);
  
  clearEditMenuItemFrame();
  loadMenu(menuName); // reload the menu items
  mEditItemGroup->setEnabled(false);
  if (selectedRow != -1 && selectedRow < mTable->rowCount())
    mTable->selectRow(selectedRow);
  
  configDirty = true;
}

void EdgyConfig::loadMenu(const QString& name) {
  QList<QPair<QString, QString> > items = conf.menuItems(name);
  
  mTable->clearContents();
  
  mTable->setRowCount(items.count());
  for (int i = 0; i < items.count(); i++) {
    mTable->setItem(i, 0, new QTableWidgetItem(items[i].first));
    mTable->setItem(i, 1, new QTableWidgetItem(items[i].second));
  }
}

void EdgyConfig::clearEditMenuItemFrame() {
  mLabelLine->clear();
  mkKeyCaptureBtn->setText(GUIConfigGlobals::CAPTURE_KEY_LABEL);
  mkMoveCursorCombo->setCurrentIndex(0);
  mcCmdLine->clear();
}

void EdgyConfig::warning(const QString& msg) {
  QMessageBox::warning(this, "EdgyConfigGUI",
          msg);
}

bool EdgyConfig::setActionComboToText(QComboBox* combo, const QString& text) {
  int apos = combo->findText(text, Qt::MatchExactly);
  if (apos == -1) {
    warning("Internal error: can't set action");
    return false;
  }
  
  combo->setCurrentIndex(apos);
  return true;
}




/********* EFFECTS TAB *********/

void EdgyConfig::createEffect() {
  editEffectGroup->setEnabled(true);
  eTable->clearSelection();
  eTable->reset();
  creatingNewEffectItem = true;
}

void EdgyConfig::editEffect() {
  clearEditEffectFrame();
  
  int currentRow = eTable->currentRow();
  if (currentRow != -1) {
    ConfigEffectItem* cei = conf.effectItem(currentRow);
    if (cei == NULL) {
      warning("Internal error: item doesn't exist.");
      return;
    }
    
    if (ConfigEffectItem_Key* key = dynamic_cast<ConfigEffectItem_Key*>(cei)) {
      if (!setActionComboToText(eActionCombo, GUIConfigGlobals::EFFECT_KEY_ACTION_LABEL))
        return;
      
      QStringList keys;
      keys.append(key->k1);
      if (key->k2 != "")
        keys.append(key->k2);
      if (key->k3 != "")
        keys.append(key->k3);
      ekKeyCaptureBtn->setKeys(keys);
      if (key->p) {
        int ppos = ekMoveCursorCombo->findText(key->p->name().c_str(), Qt::MatchExactly);
        if (ppos == -1) {
          warning("Internal error: can't set location");
          return;
        }
        ekMoveCursorCombo->setCurrentIndex(ppos);
        eActionStack->setCurrentIndex(0);
      }
    } else if (ConfigEffectItem_SKey* skey = dynamic_cast<ConfigEffectItem_SKey*>(cei)) {
      if (!setActionComboToText(eActionCombo, GUIConfigGlobals::EFFECT_SKEY_ACTION_LABEL))
        return;
      
      eskModLineEdit->setText(skey->m);
      
      int b1pos = eskM1Combo->findText(skey->b1);
      if (b1pos == -1) {
        warning("Internal error: can't set mouse button 1");
        return;
      }
      eskM1Combo->setCurrentIndex(b1pos);
      
      eskK1LineEdit->setText(skey->k1);
      
      int b2pos = eskM2Combo->findText(skey->b2);
      if (b2pos == -1) {
        warning("Internal error: can't set mouse button 2");
        return;
      }
      eskM2Combo->setCurrentIndex(b2pos);
      
      eskK2LineEdit->setText(skey->k2);
      
      eActionStack->setCurrentIndex(1);
    } else if (ConfigEffectItem_Cmd* cmd = dynamic_cast<ConfigEffectItem_Cmd*>(cei)) {
      if (!setActionComboToText(eActionCombo, GUIConfigGlobals::EFFECT_CMD_ACTION_LABEL))
        return;
      
      ecCmdLine->setText(cmd->cmd);
      eActionStack->setCurrentIndex(2);
    } else if (ConfigEffectItem_Menu* menu = dynamic_cast<ConfigEffectItem_Menu*>(cei)) {
      if (!setActionComboToText(eActionCombo, GUIConfigGlobals::EFFECT_MENU_ACTION_LABEL))
        return;
      
      int pos = emMenuCombo->findText(menu->menuName);
      if (pos == -1) {
        warning("Internal error: can't set menu");
        return;
      }
      emMenuCombo->setCurrentIndex(pos);
      eActionStack->setCurrentIndex(3);
    } else if (ConfigEffectItem_Gestures* ges = dynamic_cast<ConfigEffectItem_Gestures*>(cei)) {
      if (!setActionComboToText(eActionCombo, GUIConfigGlobals::EFFECT_GESTURES_ACTION_LABEL))
        return;
      
      int mpos = egMenuCombo->findText(ges->menuName);
      if (mpos == -1) {
        warning("Internal error: can't set menu");
        return;
      }
      egMenuCombo->setCurrentIndex(mpos);
      
      int cpos = egCursorCombo->findText(ges->cursorSetName);
      if (cpos == -1) {
        warning("Internal error: can't set cursor set");
        return;
      }
      egCursorCombo->setCurrentIndex(cpos);
      eActionStack->setCurrentIndex(4);
    } else {
      warning("Can't have this type of item in an effect.");
      return;
    }
    
    QString winclass = conf.effectWindowClass(currentRow);
    if (winclass.isEmpty())
      eWinClassBtn->setText(GUIConfigGlobals::WINDOW_CLASS_KEY_LABEL);
    else
      eWinClassBtn->setText(winclass);

    QPair<QString, QString> area = conf.effectArea(currentRow);
    int mbpos = eMouseBtnCombo->findText(area.first);
    if (mbpos == -1) {
      warning("Internal error: can't set the mouse button.");
      return;
    }
    eMouseBtnCombo->setCurrentIndex(mbpos);
    int spos = eScreenAreaCombo->findText(area.second);
    if (spos == -1) {
      warning("Internal error: can't set the screen position.");
      return;
    }
    eScreenAreaCombo->setCurrentIndex(spos);
    
    editEffectGroup->setEnabled(true);
  }
}

void EdgyConfig::deleteEffect() {
  editEffectGroup->setEnabled(false);
  clearEditEffectFrame();
  
  int curRow = eTable->currentRow();
  if (curRow != -1) {
    conf.deleteEffectItem(curRow);
    eTable->removeRow(curRow);
    if (eTable->rowCount() > curRow)
      eTable->selectRow(curRow);
    else if (eTable->rowCount() == curRow)
      eTable->selectRow(curRow-1);
    
    configDirty = true;
  }
}

void EdgyConfig::selectWindow() {
  // first ask if we really want to do this
  int answer = QMessageBox::question(this, "EdgyConfigGUI",
          "Click or focus on the target window.\n"
          "After 3 seconds it will be recorded here.\nProceed?",
          QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
  if (answer == QMessageBox::Yes)
    QTimer::singleShot(3000, this, SLOT(captureWindow()));
}
void EdgyConfig::captureWindow() {
  if (!editEffectGroup->isEnabled()) return;
  Window target;
  int dummy_int;
  XGetInputFocus(xdpy, &target, &dummy_int);
  XClassHint ch;
  if (XGetClassHint(xdpy, target, &ch)) {
    if (ch.res_name) XFree(ch.res_name);
    if (ch.res_class) {
      eWinClassBtn->setText(ch.res_class);
      XFree(ch.res_class);
    }
  } else {
    warning("Could not record the target window.\nIt either wasn't selected or does not\n"
            "have a window class hint set.");
  }
  parent->raise();
  parent->showNormal();
  parent->setFocus();
}

void EdgyConfig::openEffectKeyCaptureDlg() {
  KeyCaptureDlg dlg(this);
  dlg.keyLineEdit->ignoreModifiers(true);
  dlg.m1LineEdit->ignoreRegulars(true);
  dlg.m2LineEdit->ignoreRegulars(true);
  if (dlg.exec() == QDialog::Accepted) {
    QStringList keys = dlg.keys();
    ekKeyCaptureBtn->setKeys(keys);
    dlg.resetKeys();
  }
}

void EdgyConfig::toggleEffectListButtons() {
  if (eTable->selectedItems().size() == 0) {
    eEditBtn->setEnabled(false);
    eDeleteBtn->setEnabled(false);
  } else {
    eEditBtn->setEnabled(true);
    eDeleteBtn->setEnabled(true);
  }
}

void EdgyConfig::clearEditEffectFrame() {
  ekKeyCaptureBtn->setText(GUIConfigGlobals::CAPTURE_KEY_LABEL);
  ekMoveCursorCombo->setCurrentIndex(0);
  eskModLineEdit->clear();
  eskK1LineEdit->clear();
  eskK2LineEdit->clear();
  eskM1Combo->setCurrentIndex(0);
  eskM2Combo->setCurrentIndex(0);
  ecCmdLine->clear();
  eWinClassBtn->setText(GUIConfigGlobals::WINDOW_CLASS_KEY_LABEL);
}

void EdgyConfig::saveEffect() {
  QString place = eScreenAreaCombo->currentText();
  QString button = eMouseBtnCombo->currentText();
  QString winclass = eWinClassBtn->text();
  
  QString moveTo, tmp1, tmp2, tmp3;
  QStringList keys, buttons;
  
  
  Trigger* trigger = conf.createTrigger(button, place,
          (winclass == GUIConfigGlobals::WINDOW_CLASS_KEY_LABEL) ? NULL : winclass);
  if (trigger == NULL) {
    warning("Could not create trigger.");
    return;
  }
  
  int selectedRow = creatingNewEffectItem ? -1 : eTable->currentRow();
  creatingNewEffectItem = false;
  
  switch (eActionCombo->currentIndex()) {
    case 0: // press a key
      if (ekKeyCaptureBtn->text() == GUIConfigGlobals::CAPTURE_KEY_LABEL) {
        warning("Key was not specified.");
        delete trigger;
        return;
      }
      
      moveTo = ekMoveCursorCombo->currentText();
      
      keys = ekKeyCaptureBtn->getKeys();
      if (keys.size() == 0) {
        warning("No keystrokes specified for the key.");
        delete trigger;
        return;
      }
      
      conf.setEffect_Key(trigger, keys, (ekMoveCursorCombo->currentIndex() == 0 ? NULL : moveTo),
              selectedRow);
      break;
    
    case 1: // press a stateful key
      tmp1 = eskModLineEdit->text();
      tmp2 = eskK1LineEdit->text();
      tmp3 = eskK2LineEdit->text();
      
      if (tmp1.isEmpty() || tmp2.isEmpty() || tmp3.isEmpty()) {
        warning("All keys need to be specified for a stateful key action.");
        delete trigger;
        return;
      }
      
      keys.append(tmp1);
      keys.append(tmp2);
      keys.append(tmp3);
      
      buttons.append(eskM1Combo->currentText());
      buttons.append(eskM2Combo->currentText());
      
      conf.setEffect_SKey(trigger, keys, buttons, selectedRow);
      break;
      
    case 2: // command line
      tmp1 = ecCmdLine->text();
      if (tmp1.trimmed() == "") {
        warning("Command is empty");
        delete trigger;
        return;
      }
      
      conf.setEffect_Cmd(trigger, tmp1, selectedRow);
      break;
      
    case 3: // vertical menu
      tmp1 = emMenuCombo->currentText();
      if (tmp1.isEmpty()) {
        warning("No menu selected.");
        delete trigger;
        return;
      }
      
      conf.setEffect_Menu(trigger, tmp1, selectedRow);
      break;
      
    case 4: // gestures
      tmp1 = egMenuCombo->currentText();
      tmp2 = egCursorCombo->currentText();
      if (tmp1.isEmpty() || tmp2.isEmpty()) {
        warning("Both the menu and the cursors need to be specified.");
        delete trigger;
        return;
      }
      
      conf.setEffect_Gesture(trigger, tmp1, tmp2, selectedRow);
      break;
      
    default:
      warning("No such action. Internal error.");
      delete trigger;
      return;
  }
  
  clearEditEffectFrame();
  editEffectGroup->setEnabled(false);
  loadEffects();
  if (selectedRow != -1 && selectedRow < eTable->rowCount())
    eTable->selectRow(selectedRow);
  
  configDirty = true;
}

void EdgyConfig::loadEffects() {
  QList<QStringList> items = conf.effects();
  
  eTable->clearContents();
  
  eTable->setRowCount(items.count());
  for (int i = 0; i < items.count(); i++) {
    eTable->setItem(i, 0, new QTableWidgetItem(items[i][0]));
    eTable->setItem(i, 1, new QTableWidgetItem(items[i][1]));
    eTable->setItem(i, 2, new QTableWidgetItem(items[i][2]));
  }
}




/********* MENU ACTIONS *********/

void EdgyConfig::newConfig() {
  askUnsavedChanges();
  
  conf.createBlankConfig();
}

bool EdgyConfig::saveConfig() {
  if (!conf.destinationIsSet()) {
    QFileDialog d(this);
    d.setFileMode(QFileDialog::AnyFile);
    d.setFilter("YAML configuration (*.yml *.yaml)");
    d.setWindowTitle("Configuration File Name");
    QStringList fnames;
    if (d.exec()) {
      fnames = d.selectedFiles();
      conf.setDestinationFile(fnames[0]);
    } else return false;
  }
  
  QString error = conf.writeConfig();
  if (error != "") {
    warning("Could not write the configuration file. Error:\n" + error);
    return false;
  }
  return true;
}

void EdgyConfig::saveConfigAs() {
  QString curDest = conf.destination();
  conf.setDestinationFile("");
  if (!saveConfig())
    conf.setDestinationFile(curDest);
}

void EdgyConfig::loadConfig() {
  askUnsavedChanges();
  
  QFileDialog d(this);
  d.setFileMode(QFileDialog::ExistingFile);
  d.setFilter("YAML configuration (*.yml *.yaml)");
  d.setWindowTitle("Configuration File Name");
  
  QStringList fnames;
  if (d.exec()) {
    fnames = d.selectedFiles();
    conf.setDestinationFile(fnames[0]);
    
    ConfigParser* newparser = new ConfigParser(conf.destination().toStdString().c_str());
    if (newparser->ioerror) {
      warning(QString("Unable to open %1.\nI/O error.").arg(conf.destination()));
      conf.setDestinationFile(""); // implies that no file set
      return;
    }
    
    bool success = newparser->parse();
    if (success) {
      if (parser) // replace the current parser only on success since it stores the configdata
        delete parser;
      parser = newparser;
      conf.useConfig(parser->getConfig());
      
      QStringList cursorSets = conf.cursorSets();
      QString c;
      for (int i = 0; i < cursorSets.size(); i++) {
        c = cursorSets[i];
        cCursorSetCombo->addItem(c);
        egCursorCombo->addItem(c);
        loadCursors(c);
      }
      if (cursorSets.size() > 0)
        cCursorSetCombo->setCurrentIndex(0);
      
      QStringList menus = conf.menus();
      QString m;
      for (int i = 0; i < menus.size(); i++) {
        m = menus[i];
        mMenuCombo->addItem(m);
        emMenuCombo->addItem(m);
        egMenuCombo->addItem(m);
        loadMenu(m);
      }
      if (menus.size() > 0)
        mMenuCombo->setCurrentIndex(0);
      
      loadEffects();
      
    } else {
      warning(QString("Could not parse %1").arg(conf.destination()));
      conf.setDestinationFile("");
      return;
    }
  };
}


/********* Config helpers *********/

void EdgyConfig::askUnsavedChanges() {
  checkConfig:
  if (configDirty) {
    int ret = QMessageBox::question(this, "Save Changes?",
            "You have unsaved changes. Would you like to save them?",
            QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
    if (ret == QMessageBox::Yes) {
      if (!saveConfig())
        goto checkConfig;
    }
  }
}


/********* GLOBAL HELPERS *********/
QList<QListWidgetItem*> createListItemsFromCursors(QWidget& widget, QStringList cursorNames) {
  // libXcursor needs the theme to find the cursor
  char* theme = XcursorGetTheme(widget.x11Info().display());
  // default cursor size on the system
  int size = XcursorGetDefaultSize(widget.x11Info().display());
  
  QList<QListWidgetItem*> ret;
  
  QListWidgetItem *item;
  XcursorImage* im;
  QStringList::const_iterator i;
  for (i = cursorNames.begin(); i != cursorNames.end(); i++) {
    item = new QListWidgetItem;
    item->setText(*i);
    
    // Load the cursor image via libXcursor. Create a QImage out of it. Then convert that into
    // a QPixmap. We can't seem to go directly to QPixmap because QPixmap::fromData returns false.
    // Then set icon using the pixmap, since it easily converts into a QIcon.
    im = XcursorLibraryLoadImage(i->toStdString().c_str(), theme, size);
    if (im != NULL) {
      QImage img(reinterpret_cast<uchar*>(im->pixels),
              im->width,
              im->height,
              QImage::Format_ARGB32);
      item->setIcon(QPixmap::fromImage(img));
    }
    
    ret.append(item);
  }
  
  return ret;
}

